<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_25</span></h2>

  <p>其他按钮“按下”事件。但有一个问题，那就是go()是永远不会返回的，因为它被设计成一个无限循环。这意味着actionPerformed()根本不会返回。由于在第一个按键以后便陷入actionPerformed()中，所以程序不能再对其他任何事件进行控制（如果想出来，必须以某种方式“杀死”进程――最简便的方式就是在控制台窗口按Ctrl＋C键）。</p>

  <p>这里最基本的问题是go()需要继续执行自己的操作，而与此同时，它也需要返回，以便actionPerformed()能够完成，而且用户界面也能继续响应用户的操作。但对象go()这样的传统方法来说，它却不能在继续的同时将控制权返回给程序的其他部分。这听起来似乎是一件不可能做到的事情，就象CPU必须同时位于两个地方一样，但线程可以解决一切。“线程模型”（以及Java中的编程支持）是一种程序编写规范，可在单独一个程序里实现几个操作的同时进行。根据这一机制，CPU可为每个线程都分配自己的一部分时间。每个线程都“感觉”自己好象拥有整个CPU，但CPU的计算时间实际却是在所有线程间分摊的。</p>

  <p>线程机制多少降低了一些计算效率，但无论程序的设计，资源的均衡，还是用户操作的方便性，都从中获得了巨大的利益。综合考虑，这一机制是非常有价值的。当然，如果本来就安装了多块CPU，那么操作系统能够自行决定为不同的CPU分配哪些线程，程序的总体运行速度也会变得更快（所有这些都要求操作系统以及应用程序的支持）。多线程和多任务是充分发挥多处理机系统能力的一种最有效的方式。</p>

  <p>14.1.1 从线程继承</p>

  <p>为创建一个线程，最简单的方法就是从Thread类继承。这个类包含了创建和运行线程所需的一切东西。Thread最重要的方法是run()。但为了使用run()，必须对其进行过载或者覆盖，使其能充分按自己的吩咐行事。因此，run()属于那些会与程序中的其他线程“并发”或“同时”执行的代码。</p>

  <p>下面这个例子可创建任意数量的线程，并通过为每个线程分配一个独一无二的编号（由一个静态变量产生），从而对不同的线程进行跟踪。Thread的run()方法在这里得到了覆盖，每通过一次循环，计数就减1――计数为0时则完成循环（此时一旦返回run()，线程就中止运行）。</p>

  <p>//: SimpleThread.java</p>

  <p>// Very simple Threading example</p>

  <p>public class SimpleThread extends Thread {</p>

  <p>private int countDown = 5;</p>

  <p>private int threadNumber;</p>

  <p>private static int threadCount = 0;</p>

  <p>public SimpleThread() {</p>

  <p>threadNumber = ++threadCount;</p>

  <p>System.out.println("Making " + threadNumber);</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>System.out.println("Thread " +</p>

  <p>threadNumber + "(" + countDown + ")");</p>

  <p>if(--countDown == 0) return;</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>for(int i = 0; i &lt; 5; i++)</p>

  <p>new SimpleThread().start();</p>

  <p>System.out.println("All Threads Started");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>run()方法几乎肯定含有某种形式的循环――它们会一直持续到线程不再需要为止。因此，我们必须规定特定的条件，以便中断并退出这个循环（或者在上述的例子中，简单地从run()返回即可）。run()通常采用一种无限循环的形式。也就是说，通过阻止外部发出对线程的stop()或者destroy()调用，它会永远运行下去（直到程序完成）。</p>

  <p>在main()中，可看到创建并运行了大量线程。Thread包含了一个特殊的方法，叫作start()，它的作用是对线程进行特殊的初始化，然后调用run()。所以整个步骤包括：调用构建器来构建对象，然后用start()配置线程，再调用run()。如果不调用start()――如果适当的话，可在构建器那样做――线程便永远不会启动。</p>

  <p>下面是该程序某一次运行的输出（注意每次运行都会不同）：</p>

  <p>Making 1</p>

  <p>Making 2</p>

  <p>Making 3</p>

  <p>Making 4</p>

  <p>Making 5</p>

  <p>Thread 1(5)</p>

  <p>Thread 1(4)</p>

  <p>Thread 1(3)</p>

  <p>Thread 1(2)</p>

  <p>Thread 2(5)</p>

  <p>Thread 2(4)</p>

  <p>Thread 2(3)</p>

  <p>Thread 2(2)</p>

  <p>Thread 2(1)</p>

  <p>Thread 1(1)</p>

  <p>All Threads Started</p>

  <p>Thread 3(5)</p>

  <p>Thread 4(5)</p>

  <p>Thread 4(4)</p>

  <p>Thread 4(3)</p>

  <p>Thread 4(2)</p>

  <p>Thread 4(1)</p>

  <p>Thread 5(5)</p>

  <p>Thread 5(4)</p>

  <p>Thread 5(3)</p>

  <p>Thread 5(2)</p>

  <p>Thread 5(1)</p>

  <p>Thread 3(4)</p>

  <p>Thread 3(3)</p>

  <p>Thread 3(2)</p>

  <p>Thread 3(1)</p>

  <p>可注意到这个例子中到处都调用了sleep()，然而输出结果指出每个线程都获得了属于自己的那一部分CPU执行时间。从中可以看出，尽管sleep()依赖一个线程的存在来执行，但却与允许或禁止线程无关。它只不过是另一个不同的方法而已。</p>

  <p>亦可看出线程并不是按它们创建时的顺序运行的。事实上，CPU处理一个现有线程集的顺序是不确定的――除非我们亲自介入，并用Thread的setPriority()方法调整它们的优先级。</p>

  <p>main()创建Thread对象时，它并未捕获任何一个对象的句柄。普通对象对于垃圾收集来说是一种“公平竞赛”，但线程却并非如此。每个线程都会“注册”自己，所以某处实际存在着对它的一个引用。这样一来，垃圾收集器便只好对它“瞠目以对”了。</p>

  <p>14.1.2 针对用户界面的多线程</p>

  <p>现在，我们也许能用一个线程解决在Counter1.java中出现的问题。采用的一个技巧便是在一个线程的run()方法中放置“子任务”――亦即位于go()内的循环。一旦用户按下Start按钮，线程就会启动，但马上结束线程的创建。这样一来，尽管线程仍在运行，但程序的主要工作却能得以继续（等候并响应用户界面的事件）。下面是具体的代码：</p>

  <p>//: Counter2.java</p>

  <p>// A responsive user interface with threads</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>class SeparateSubTask extends Thread {</p>

  <p>private int count = 0;</p>

  <p>private Counter2 c2;</p>

  <p>private boolean runFlag = true;</p>

  <p>public SeparateSubTask(Counter2 c2) {</p>

  <p>this.c2 = c2;</p>

  <p>start();</p>

  <p>}</p>

  <p>public void invertFlag() { runFlag = !runFlag;}</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>try {</p>

  <p>sleep(100);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>if(runFlag)</p>

  <p>c2.t.setText(Integer.toString(count++));</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public class Counter2 extends Applet {</p>

  <p>TextField t = new TextField(10);</p>

  <p>private SeparateSubTask sp = null;</p>

  <p>private Button</p>

  <p>onOff = new Button("Toggle"),</p>

  <p>start = new Button("Start");</p>

  <p>public void init() {</p>

  <p>add(t);</p>

  <p>start.addActionListener(new StartL());</p>

  <p>add(start);</p>

  <p>onOff.addActionListener(new OnOffL());</p>

  <p>add(onOff);</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(sp == null)</p>

  <p>sp = new SeparateSubTask(Counter2.this);</p>

  <p>}</p>

  <p>}</p>

  <p>class OnOffL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(sp != null)</p>

  <p>sp.invertFlag();</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Counter2 applet = new Counter2();</p>

  <p>Frame aFrame = new Frame("Counter2");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300,200);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>现在，Counter2变成了一个相当直接的程序，它的唯一任务就是设置并管理用户界面。但假若用户现在按下Start按钮，却不会真正调用一个方法。此时不是创建类的一个线程，而是创建SeparateSubTask，然后继续Counter2事件循环。注意此时会保存SeparateSubTask的句柄，以便我们按下onOff按钮的时候，能正常地切换位于SeparateSubTask内部的runFlag（运行标志）。随后那个线程便可启动（当它看到标志的时候），然后将自己中止（亦可将SeparateSubTask设为一个内部类来达到这一目的）。</p>

  <p>SeparateSubTask类是对Thread的一个简单扩展，它带有一个构建器（其中保存了Counter2句柄，然后通过调用start()来运行线程）以及一个run()――本质上包含了Counter1.java的go()内的代码。由于SeparateSubTask知道自己容纳了指向一个Counter2的句柄，所以能够在需要的时候介入，并访问Counter2的TestField（文本字段）。</p>

  <p>按下onOff按钮，几乎立即能得到正确的响应。当然，这个响应其实并不是“立即”发生的，它毕竟和那种由“中断”驱动的系统不同。只有线程拥有CPU的执行时间，并注意到标记已发生改变，计数器才会停止。</p>

  <p>1. 用内部类改善代码</p>

  <p>下面说说题外话，请大家注意一下SeparateSubTask和Counter2类之间发生的结合行为。SeparateSubTask同Counter2“亲密”地结合到了一起――它必须持有指向自己“父”Counter2对象的一个句柄，以便自己能回调和操纵它。但两个类并不是真的合并为单独一个类（尽管在下一节中，我们会讲到Java确实提供了合并它们的方法），因为它们各自做的是不同的事情，而且是在不同的时间创建的。但不管怎样，它们依然紧密地结合到一起（更准确地说，应该叫“联合”），所以使程序代码多少显得有些笨拙。在这种情况下，一个内部类可以显著改善代码的“可读性”和执行效率：</p>

  <p>//: Counter2i.java</p>

  <p>// Counter2 using an inner class for the thread</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>public class Counter2i extends Applet {</p>

  <p>private class SeparateSubTask extends Thread {</p>

  <p>int count = 0;</p>

  <p>boolean runFlag = true;</p>

  <p>SeparateSubTask() { start(); }</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>try {</p>

  <p>sleep(100);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>if(runFlag)</p>

  <p>t.setText(Integer.toString(count++));</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>private SeparateSubTask sp = null;</p>

  <p>private TextField t = new TextField(10);</p>

  <p>private Button</p>

  <p>onOff = new Button("Toggle"),</p>

  <p>start = new Button("Start");</p>

  <p>public void init() {</p>

  <p>add(t);</p>

  <p>start.addActionListener(new StartL());</p>

  <p>add(start);</p>

  <p>onOff.addActionListener(new OnOffL());</p>

  <p>add(onOff);</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(sp == null)</p>

  <p>sp = new SeparateSubTask();</p>

  <p>}</p>

  <p>}</p>

  <p>class OnOffL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(sp != null)</p>

  <p>sp.runFlag = !sp.runFlag; // invertFlag();</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Counter2i applet = new Counter2i();</p>

  <p>Frame aFrame = new Frame("Counter2i");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300,200);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这个SeparateSubTask名字不会与前例中的SeparateSubTask冲突――即使它们都在相同的目录里――因为它已作为一个内部类隐藏起来。大家亦可看到内部类被设为private（私有）属性，这意味着它的字段和方法都可获得默认的访问权限（run()除外，它必须设为public，因为它在基础类中是公开的）。除Counter2i之外，其他任何方面都不可访问private内部类。而且由于两个类紧密结合在一起，所以很容易放宽它们之间的访问限制。在SeparateSubTask中，我们可看到invertFlag()方法已被删去，因为Counter2i现在可以直接访问runFlag。</p>

  <p>此外，注意SeparateSubTask的构建器已得到了简化――它现在唯一的用外就是启动线程。Counter2i对象的句柄仍象以前那样得以捕获，但不再是通过人工传递和引用外部对象来达到这一目的，此时的内部类机制可以自动照料它。在run()中，可看到对t的访问是直接进行的，似乎它是SeparateSubTask的一个字段。父类中的t字段现在可以变成private，因为SeparateSubTask能在未获任何特殊许可的前提下自由地访问它――而且无论如何都该尽可能地把字段变成“私有”属性，以防来自类外的某种力量不慎地改变它们。</p>

  <p>无论在什么时候，只要注意到类相互之间结合得比较紧密，就可考虑利用内部类来改善代码的编写与维护。</p>

  <p>14.1.3 用主类合并线程</p>

  <p>在上面的例子中，我们看到线程类（Thread）与程序的主类（Main）是分隔开的。这样做非常合理，而且易于理解。然而，还有另一种方式也是经常要用到的。尽管它不十分明确，但一般都要更简洁一些（这也解释了它为什么十分流行）。通过将主程序类变成一个线程，这种形式可将主程序类与线程类合并到一起。由于对一个GUI程序来说，主程序类必须从Frame或Applet继承，所以必须用一个接口加入额外的功能。这个接口叫作Runnable，其中包含了与Thread一致的基本方法。事实上，Thread也实现了Runnable，它只指出有一个run()方法。</p>

  <p>对合并后的程序／线程来说，它的用法不是十分明确。当我们启动程序时，会创建一个Runnable（可运行的）对象，但不会自行启动线程。线程的启动必须明确进行。下面这个程序向我们演示了这一点，它再现了Counter2的功能：</p>

  <p>//: Counter3.java</p>

  <p>// Using the Runnable interface to turn the</p>

  <p>// main class into a thread.</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>public class Counter3</p>

  <p>extends Applet implements Runnable {</p>

  <p>private int count = 0;</p>

  <p>private boolean runFlag = true;</p>

  <p>private Thread selfThread = null;</p>

  <p>private Button</p>

  <p>onOff = new Button("Toggle"),</p>

  <p>start = new Button("Start");</p>

  <p>private TextField t = new TextField(10);</p>

  <p>public void init() {</p>

  <p>add(t);</p>

  <p>start.addActionListener(new StartL());</p>

  <p>add(start);</p>

  <p>onOff.addActionListener(new OnOffL());</p>

  <p>add(onOff);</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>try {</p>

  <p>selfThread.sleep(100);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>if(runFlag)</p>

  <p>t.setText(Integer.toString(count++));</p>

  <p>}</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(selfThread == null) {</p>

  <p>selfThread = new Thread(Counter3.this);</p>

  <p>selfThread.start();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class OnOffL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>runFlag = !runFlag;</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Counter3 applet = new Counter3();</p>

  <p>Frame aFrame = new Frame("Counter3");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300,200);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>现在run()位于类内，但它在init()结束以后仍处在“睡眠”状态。若按下启动按钮，线程便会用多少有些暧昧的表达方式创建（若线程尚不存在）：</p>

  <p>new Thread(Counter3.this);</p>

  <p>若某样东西有一个Runnable接口，实际只是意味着它有一个run()方法，但不存在与之相关的任何特殊东西――它不具有任何天生的线程处理能力，这与那些从Thread继承的类是不同的。所以为了从一个Runnable对象产生线程，必须单独创建一个线程，并为其传递Runnable对象；可为其使用一个特殊的构建器，并令其采用一个Runnable作为自己的参数使用。随后便可为那个线程调用start()，如下所示：</p>

  <p>selfThread.start();</p>

  <p>它的作用是执行常规初始化操作，然后调用run()。</p>

  <p>Runnable接口最大的一个优点是所有东西都从属于相同的类。若需访问什么东西，只需简单地访问它即可，不需要涉及一个独立的对象。但为这种便利也是要付出代价的――只可为那个特定的对象运行单独一个线程（尽管可创建那种类型的多个对象，或者在不同的类里创建其他对象）。</p>

  <p>注意Runnable接口本身并不是造成这一限制的罪魁祸首。它是由于Runnable与我们的主类合并造成的，因为每个应用只能主类的一个对象。</p>

  <p>14.1.4 制作多个线程</p>

  <p>现在考虑一下创建多个不同的线程的问题。我们不可用前面的例子来做到这一点，所以必须倒退回去，利用从Thread继承的多个独立类来封装run()。但这是一种更常规的方案，而且更易理解，所以尽管前例揭示了我们经常都能看到的编码样式，但并不推荐在大多数情况下都那样做，因为它只是稍微复杂一些，而且灵活性稍低一些。</p>

  <p>下面这个例子用计数器和切换按钮再现了前面的编码样式。但这一次，一个特定计数器的所有信息（按钮和文本字段）都位于它自己的、从Thread继承的对象内。Ticker中的所有字段都具有private（私有）属性，这意味着Ticker的具体实现方案可根据实际情况任意修改，其中包括修改用于获取和显示信息的数据组件的数量及类型。创建好一个Ticker对象以后，构建器便请求一个AWT容器（Container）的句柄――Ticker用自己的可视组件填充那个容器。采用这种方式，以后一旦改变了可视组件，使用Ticker的代码便不需要另行修改一道。</p>

  <p>//: Counter4.java</p>

  <p>// If you separate your thread from the main</p>

  <p>// class, you can have as many threads as you</p>

  <p>// want.</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>class Ticker extends Thread {</p>

  <p>private Button b = new Button("Toggle");</p>

  <p>private TextField t = new TextField(10);</p>

  <p>private int count = 0;</p>

  <p>private boolean runFlag = true;</p>

  <p>public Ticker(Container c) {</p>

  <p>b.addActionListener(new ToggleL());</p>

  <p>Panel p = new Panel();</p>

  <p>p.add(t);</p>

  <p>p.add(b);</p>

  <p>c.add(p);</p>

  <p>}</p>

  <p>class ToggleL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>runFlag = !runFlag;</p>

  <p>}</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>if(runFlag)</p>

  <p>t.setText(Integer.toString(count++));</p>

  <p>try {</p>

  <p>sleep(100);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public class Counter4 extends Applet {</p>

  <p>private Button start = new Button("Start");</p>

  <p>private boolean started = false;</p>

  <p>private Ticker[] s;</p>

  <p>private boolean isApplet = true;</p>

  <p>private int size;</p>

  <p>public void init() {</p>

  <p>// Get parameter "size" from Web page:</p>

  <p>if(isApplet)</p>

  <p>size =</p>

  <p>Integer.parseInt(getParameter("size"));</p>

  <p>s = new Ticker[size];</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i] = new Ticker(this);</p>

  <p>start.addActionListener(new StartL());</p>

  <p>add(start);</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(!started) {</p>

  <p>started = true;</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i].start();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Counter4 applet = new Counter4();</p>

  <p>// This isn't an applet, so set the flag and</p>

  <p>// produce the parameter values from args:</p>

  <p>applet.isApplet = false;</p>

  <p>applet.size =</p>

  <p>(args.length == 0 ? 5 :</p>

  <p>Integer.parseInt(args[0]));</p>

  <p>Frame aFrame = new Frame("Counter4");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(200, applet.size * 50);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>Ticker不仅包括了自己的线程处理机制，也提供了控制与显示线程的工具。可按自己的意愿创建任意数量的线程，毋需明确地创建窗口化组件。</p>

  <p>在Counter4中，有一个名为s的Ticker对象的数组。为获得最大的灵活性，这个数组的长度是用程序片参数接触Web页而初始化的。下面是网页中长度参数大致的样子，它们嵌于对程序片（applet）的描述内容中：</p>

  <p>&lt;applet code=Counter4 width=600 height=600&gt;</p>

  <p>&lt;param name=size value="20"&gt;</p>

  <p>&lt;/applet&gt;</p>

  <p>其中，param，name和value是所有Web页都适用的关键字。name是指程序中对参数的一种引用称谓，value可以是任何字串（并不仅仅是解析成一个数字的东西）。</p>

  <p>我们注意到对数组s长度的判断是在init()内部完成的，它没有作为s的内嵌定义的一部分提供。换言之，不可将下述代码作为类定义的一部分使用（应该位于任何方法的外部）：</p>

  <p>inst size = Integer.parseInt(getParameter("Size"));</p>

  <p>Ticker[] s = new Ticker[size]</p>

  <p>可把它编译出来，但会在运行期得到一个空指针违例。但若将getParameter()初始化移入init()，则可正常工作。程序片框架会进行必要的启动工作，以便在进入init()前收集好一些参数。</p>

  <p>此外，上述代码被同时设置成一个程序片和一个应用（程序）。在它是应用程序的情况下，size参数可从命令行里提取出来（否则就提供一个默认的值）。</p>

  <p>数组的长度建好以后，就可以创建新的Ticker对象；作为Ticker构建器的一部分，用于每个Ticker的按钮和文本字段就会加入程序片。</p>

  <p>按下Start按钮后，会在整个Ticker数组里遍历，并为每个Ticker调用start()。记住，start()会进行必要的线程初始化工作，然后为那个线程调用run()。</p>

  <p>ToggleL监视器只是简单地切换Ticker中的标记，一旦对应线程以后需要修改这个标记，它会作出相应的反应。</p>

  <p>这个例子的一个好处是它使我们能够方便地创建由单独子任务构成的大型集合，并以监视它们的行为。在这种情况下，我们会发现随着子任务数量的增多，机器显示出来的数字可能会出现更大的分歧，这是由于为线程提供服务的方式造成的。</p>

  <p>亦可试着体验一下sleep(100)在Ticker.run()中的重要作用。若删除sleep()，那么在按下一个切换按钮前，情况仍然会进展良好。按下按钮以后，那个特定的线程就会出现一个失败的runFlag，而且run()会深深地陷入一个无限循环――很难在多任务处理期间中止退出。因此，程序对用户操作的反应灵敏度会大幅度降低。</p>

  <p>14.1.5 Daemon线程</p>

  <p>“Daemon”线程的作用是在程序的运行期间于后台提供一种“常规”服务，但它并不属于程序的一个基本部分。因此，一旦所有非Daemon线程完成，程序也会中止运行。相反，假若有任何非Daemon线程仍在运行（比如还有一个正在运行main()的线程），则程序的运行不会中止。</p>

  <p>通过调用isDaemon()，可调查一个线程是不是一个Daemon，而且能用setDaemon()打开或者关闭一个线程的Daemon状态。如果是一个Daemon线程，那么它创建的任何线程也会自动具备Daemon属性。</p>

  <p>下面这个例子演示了Daemon线程的用法：</p>

  <p>//: Daemons.java</p>

  <p>// Daemonic behavior</p>

  <p>import java.io.*;</p>

  <p>class Daemon extends Thread {</p>

  <p>private static final int SIZE = 10;</p>

  <p>private Thread[] t = new Thread[SIZE];</p>

  <p>public Daemon() {</p>

  <p>setDaemon(true);</p>

  <p>start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>for(int i = 0; i &lt; SIZE; i++)</p>

  <p>t[i] = new DaemonSpawn(i);</p>

  <p>for(int i = 0; i &lt; SIZE; i++)</p>

  <p>System.out.println(</p>

  <p>"t[" + i + "].isDaemon() = "</p>

  <p>+ t[i].isDaemon());</p>

  <p>while(true)</p>

  <p>yield();</p>

  <p>}</p>

  <p>}</p>

  <p>class DaemonSpawn extends Thread {</p>

  <p>public DaemonSpawn(int i) {</p>

  <p>System.out.println(</p>

  <p>"DaemonSpawn " + i + " started");</p>

  <p>start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true)</p>

  <p>yield();</p>

  <p>}</p>

  <p>}</p>

  <p>public class Daemons {</p>

  <p>public static void main(String[] args) {</p>

  <p>Thread d = new Daemon();</p>

  <p>System.out.println(</p>

  <p>"d.isDaemon() = " + d.isDaemon());</p>

  <p>// Allow the daemon threads to finish</p>

  <p>// their startup processes:</p>

  <p>BufferedReader stdin =</p>

  <p>new BufferedReader(</p>

  <p>new InputStreamReader(System.in));</p>

  <p>System.out.println("Waiting for CR");</p>

  <p>try {</p>

  <p>stdin.readLine();</p>

  <p>} catch(IOException e) {}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>Daemon线程可将自己的Daemon标记设置成“真”，然后产生一系列其他线程，而且认为它们也具有Daemon属性。随后，它进入一个无限循环，在其中调用yield()，放弃对其他进程的控制。在这个程序早期的一个版本中，无限循环会使int计数器增值，但会使整个程序都好象陷入停顿状态。换用yield()后，却可使程序充满“活力”，不会使人产生停滞或反应迟钝的感觉。</p>

  <p>一旦main()完成自己的工作，便没有什么能阻止程序中断运行，因为这里运行的只有Daemon线程。所以能看到启动所有Daemon线程后显示出来的结果，System.in也进行了相应的设置，使程序中断前能等待一个回车。如果不进行这样的设置，就只能看到创建Daemon线程的一部分结果（试试将readLine()代码换成不同长度的sleep()调用，看看会有什么表现）。</p>

  <p>14.2 共享有限的资源</p>

  <p>可将单线程程序想象成一种孤立的实体，它能遍历我们的问题空间，而且一次只能做一件事情。由于只有一个实体，所以永远不必担心会有两个实体同时试图使用相同的资源，就象两个人同时都想停到一个车位，同时都想通过一扇门，甚至同时发话。</p>

  <p>进入多线程环境后，它们则再也不是孤立的。可能会有两个甚至更多的线程试图同时同一个有限的资源。必须对这种潜在资源冲突进行预防，否则就可能发生两个线程同时访问一个银行帐号，打印到同一台计算机，以及对同一个值进行调整等等。</p>

  <p>14.2.1 资源访问的错误方法</p>

  <p>现在考虑换成另一种方式来使用本章频繁见到的计数器。在下面的例子中，每个线程都包含了两个计数器，它们在run()里增值以及显示。除此以外，我们使用了Watcher类的另一个线程。它的作用是监视计数器，检查它们是否保持相等。这表面是一项无意义的行动，因为如果查看代码，就会发现计数器肯定是相同的。但实际情况却不一定如此。下面是程序的第一个版本：</p>

  <p>//: Sharing1.java</p>

  <p>// Problems with resource sharing while threading</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>class TwoCounter extends Thread {</p>

  <p>private boolean started = false;</p>

  <p>private TextField</p>

  <p>t1 = new TextField(5),</p>

  <p>t2 = new TextField(5);</p>

  <p>private Label l =</p>

  <p>new Label("count1 == count2");</p>

  <p>private int count1 = 0, count2 = 0;</p>

  <p>// Add the display components as a panel</p>

  <p>// to the given container:</p>

  <p>public TwoCounter(Container c) {</p>

  <p>Panel p = new Panel();</p>

  <p>p.add(t1);</p>

  <p>p.add(t2);</p>

  <p>p.add(l);</p>

  <p>c.add(p);</p>

  <p>}</p>

  <p>public void start() {</p>

  <p>if(!started) {</p>

  <p>started = true;</p>

  <p>super.start();</p>

  <p>}</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>t1.setText(Integer.toString(count1++));</p>

  <p>t2.setText(Integer.toString(count2++));</p>

  <p>try {</p>

  <p>sleep(500);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>public void synchTest() {</p>

  <p>Sharing1.incrementAccess();</p>

  <p>if(count1 != count2)</p>

  <p>l.setText("Unsynched");</p>

  <p>}</p>

  <p>}</p>

  <p>class Watcher extends Thread {</p>

  <p>private Sharing1 p;</p>

  <p>public Watcher(Sharing1 p) {</p>

  <p>this.p = p;</p>

  <p>start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>for(int i = 0; i &lt; p.s.length; i++)</p>

  <p>p.s[i].synchTest();</p>

  <p>try {</p>

  <p>sleep(500);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public class Sharing1 extends Applet {</p>

  <p>TwoCounter[] s;</p>

  <p>private static int accessCount = 0;</p>

  <p>private static TextField aCount =</p>

  <p>new TextField("0", 10);</p>

  <p>public static void incrementAccess() {</p>

  <p>accessCount++;</p>

  <p>aCount.setText(Integer.toString(accessCount));</p>

  <p>}</p>

  <p>private Button</p>

  <p>start = new Button("Start"),</p>

  <p>observer = new Button("Observe");</p>

  <p>private boolean isApplet = true;</p>

  <p>private int numCounters = 0;</p>

  <p>private int numObservers = 0;</p>

  <p>public void init() {</p>

  <p>if(isApplet) {</p>

  <p>numCounters =</p>

  <p>Integer.parseInt(getParameter("size"));</p>

  <p>numObservers =</p>

  <p>Integer.parseInt(</p>

  <p>getParameter("observers"));</p>

  <p>}</p>

  <p>s = new TwoCounter[numCounters];</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i] = new TwoCounter(this);</p>

  <p>Panel p = new Panel();</p>

  <p>start.addActionListener(new StartL());</p>

  <p>p.add(start);</p>

  <p>observer.addActionListener(new ObserverL());</p>

  <p>p.add(observer);</p>

  <p>p.add(new Label("Access Count"));</p>

  <p>p.add(aCount);</p>

  <p>add(p);</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i].start();</p>

  <p>}</p>

  <p>}</p>

  <p>class ObserverL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>for(int i = 0; i &lt; numObservers; i++)</p>

  <p>new Watcher(Sharing1.this);</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Sharing1 applet = new Sharing1();</p>

  <p>// This isn't an applet, so set the flag and</p>

  <p>// produce the parameter values from args:</p>

  <p>applet.isApplet = false;</p>

  <p>applet.numCounters =</p>

  <p>(args.length == 0 ? 5 :</p>

  <p>Integer.parseInt(args[0]));</p>

  <p>applet.numObservers =</p>

  <p>(args.length &lt; 2 ? 5 :</p>

  <p>Integer.parseInt(args[1]));</p>

  <p>Frame aFrame = new Frame("Sharing1");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e){</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(350, applet.numCounters *100);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>和往常一样，每个计数器都包含了自己的显示组件：两个文本字段以及一个标签。根据它们的初始值，可知道计数是相同的。这些组件在TwoCounter构建器加入Container。由于这个线程是通过用户的一个“按下按钮”操作启动的，所以start()可能被多次调用。但对一个线程来说，对Thread.start()的多次调用是非法的（会产生违例）。在started标记和过载的start()方法中，大家可看到针对这一情况采取的防范措施。</p>

  <p>在run()中，count1和count2的增值与显示方式表面上似乎能保持它们完全一致。随后会调用sleep()；若没有这个调用，程序便会出错，因为那会造成CPU难于交换任务。</p>

  <p>synchTest()方法采取的似乎是没有意义的行动，它检查count1是否等于count2；如果不等，就把标签设为“Unsynched”（不同步）。但是首先，它调用的是类Sharing1的一个静态成员，以便增值和显示一个访问计数器，指出这种检查已成功进行了多少次（这样做的理由会在本例的其他版本中变得非常明显）。</p>

  <p>Watcher类是一个线程，它的作用是为处于活动状态的所有TwoCounter对象都调用synchTest()。其间，它会对Sharing1对象中容纳的数组进行遍历。可将Watcher想象成它掠过TwoCounter对象的肩膀不断地“偷看”。</p>

  <p>Sharing1包含了TwoCounter对象的一个数组，它通过init()进行初始化，并在我们按下“start”按钮后作为线程启动。以后若按下“Observe”（观察）按钮，就会创建一个或者多个观察器，并对毫不设防的TwoCounter进行调查。</p>

  <p>注意为了让它作为一个程序片在浏览器中运行，Web页需要包含下面这几行：</p>

  <p>&lt;applet code=Sharing1 width=650 height=500&gt;</p>

  <p>&lt;param name=size value="20"&gt;</p>

  <p>&lt;param name=observers value="1"&gt;</p>

  <p>&lt;/applet&gt;</p>

  <p>可自行改变宽度、高度以及参数，根据自己的意愿进行试验。若改变了size和observers，程序的行为也会发生变化。我们也注意到，通过从命令行接受参数（或者使用默认值），它被设计成作为一个独立的应用程序运行。</p>

  <p>下面才是最让人“不可思议”的。在TwoCounter.run()中，无限循环只是不断地重复相邻的行：</p>

  <p>t1.setText(Integer.toString(count1++));</p>

  <p>t2.setText(Integer.toString(count2++));</p>

  <p>（和“睡眠”一样，不过在这里并不重要）。但在程序运行的时候，你会发现count1和count2被“观察”（用Watcher观察）的次数是不相等的！这是由线程的本质造成的――它们可在任何时候挂起（暂停）。所以在上述两行的执行时刻之间，有时会出现执行暂停现象。同时，Watcher线程也正好跟随着进来，并正好在这个时候进行比较，造成计数器出现不相等的情况。</p>

  <p>本例揭示了使用线程时一个非常基本的问题。我们跟无从知道一个线程什么时候运行。想象自己坐在一张桌子前面，桌上放有一把叉子，准备叉起自己的最后一块食物。当叉子要碰到食物时，食物却突然消失了（因为这个线程已被挂起，同时另一个线程进来“偷”走了食物）。这便是我们要解决的问题。</p>

  <p>有的时候，我们并不介意一个资源在尝试使用它的时候是否正被访问（食物在另一些盘子里）。但为了让多线程机制能够正常运转，需要采取一些措施来防止两个线程访问相同的资源――至少在关键的时期。</p>

  <p>为防止出现这样的冲突，只需在线程使用一个资源时为其加锁即可。访问资源的第一个线程会其加上锁以后，其他线程便不能再使用那个资源，除非被解锁。如果车子的前座是有限的资源，高喊“这是我的！”的孩子会主张把它锁起来。</p>

  <p>14.2.2 Java如何共享资源</p>

  <p>对一种特殊的资源――对象中的内存――Java提供了内建的机制来防止它们的冲突。由于我们通常将数据元素设为从属于private（私有）类，然后只通过方法访问那些内存，所以只需将一个特定的方法设为synchronized（同步的），便可有效地防止冲突。在任何时刻，只可有一个线程调用特定对象的一个synchronized方法（尽管那个线程可以调用多个对象的同步方法）。下面列出简单的synchronized方法：</p>

  <p>synchronized void f() { /* ... */ }</p>

  <p>synchronized void g() { /* ... */ }</p>

  <p>每个对象都包含了一把锁（也叫作“监视器”），它自动成为对象的一部分（不必为此写任何特殊的代码）。调用任何synchronized方法时，对象就会被锁定，不可再调用那个对象的其他任何synchronized方法，除非第一个方法完成了自己的工作，并解除锁定。在上面的例子中，如果为一个对象调用f()，便不能再为同样的对象调用g()，除非f()完成并解除锁定。因此，一个特定对象的所有synchronized方法都共享着一把锁，而且这把锁能防止多个方法对通用内存同时进行写操作（比如同时有多个线程）。</p>

  <p>每个类也有自己的一把锁（作为类的Class对象的一部分），所以synchronized static方法可在一个类的范围内被相互间锁定起来，防止与static数据的接触。</p>

  <p>注意如果想保护其他某些资源不被多个线程同时访问，可以强制通过synchronized方访问那些资源。</p>

  <p>1. 计数器的同步</p>

  <p>装备了这个新关键字后，我们能够采取的方案就更灵活了：可以只为TwoCounter中的方法简单地使用synchronized关键字。下面这个例子是对前例的改版，其中加入了新的关键字：</p>

  <p>//: Sharing2.java</p>

  <p>// Using the synchronized keyword to prevent</p>

  <p>// multiple access to a particular resource.</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>class TwoCounter2 extends Thread {</p>

  <p>private boolean started = false;</p>

  <p>private TextField</p>

  <p>t1 = new TextField(5),</p>

  <p>t2 = new TextField(5);</p>

  <p>private Label l =</p>

  <p>new Label("count1 == count2");</p>

  <p>private int count1 = 0, count2 = 0;</p>

  <p>public TwoCounter2(Container c) {</p>

  <p>Panel p = new Panel();</p>

  <p>p.add(t1);</p>

  <p>p.add(t2);</p>

  <p>p.add(l);</p>

  <p>c.add(p);</p>

  <p>}</p>

  <p>public void start() {</p>

  <p>if(!started) {</p>

  <p>started = true;</p>

  <p>super.start();</p>

  <p>}</p>

  <p>}</p>

  <p>public synchronized void run() {</p>

  <p>while (true) {</p>

  <p>t1.setText(Integer.toString(count1++));</p>

  <p>t2.setText(Integer.toString(count2++));</p>

  <p>try {</p>

  <p>sleep(500);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>public synchronized void synchTest() {</p>

  <p>Sharing2.incrementAccess();</p>

  <p>if(count1 != count2)</p>

  <p>l.setText("Unsynched");</p>

  <p>}</p>

  <p>}</p>

  <p>class Watcher2 extends Thread {</p>

  <p>private Sharing2 p;</p>

  <p>public Watcher2(Sharing2 p) {</p>

  <p>this.p = p;</p>

  <p>start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>for(int i = 0; i &lt; p.s.length; i++)</p>

  <p>p.s[i].synchTest();</p>

  <p>try {</p>

  <p>sleep(500);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public class Sharing2 extends Applet {</p>

  <p>TwoCounter2[] s;</p>

  <p>private static int accessCount = 0;</p>

  <p>private static TextField aCount =</p>

  <p>new TextField("0", 10);</p>

  <p>public static void incrementAccess() {</p>

  <p>accessCount++;</p>

  <p>aCount.setText(Integer.toString(accessCount));</p>

  <p>}</p>

  <p>private Button</p>

  <p>start = new Button("Start"),</p>

  <p>observer = new Button("Observe");</p>

  <p>private boolean isApplet = true;</p>

  <p>private int numCounters = 0;</p>

  <p>private int numObservers = 0;</p>

  <p>public void init() {</p>

  <p>if(isApplet) {</p>

  <p>numCounters =</p>

  <p>Integer.parseInt(getParameter("size"));</p>

  <p>numObservers =</p>

  <p>Integer.parseInt(</p>

  <p>getParameter("observers"));</p>

  <p>}</p>

  <p>s = new TwoCounter2[numCounters];</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i] = new TwoCounter2(this);</p>

  <p>Panel p = new Panel();</p>

  <p>start.addActionListener(new StartL());</p>

  <p>p.add(start);</p>

  <p>observer.addActionListener(new ObserverL());</p>

  <p>p.add(observer);</p>

  <p>p.add(new Label("Access Count"));</p>

  <p>p.add(aCount);</p>

  <p>add(p);</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i].start();</p>

  <p>}</p>

  <p>}</p>

  <p>class ObserverL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>for(int i = 0; i &lt; numObservers; i++)</p>

  <p>new Watcher2(Sharing2.this);</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Sharing2 applet = new Sharing2();</p>

  <p>// This isn't an applet, so set the flag and</p>

  <p>// produce the parameter values from args:</p>

  <p>applet.isApplet = false;</p>

  <p>applet.numCounters =</p>

  <p>(args.length == 0 ? 5 :</p>

  <p>Integer.parseInt(args[0]));</p>

  <p>applet.numObservers =</p>

  <p>(args.length &lt; 2 ? 5 :</p>

  <p>Integer.parseInt(args[1]));</p>

  <p>Frame aFrame = new Frame("Sharing2");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e){</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(350, applet.numCounters *100);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>我们注意到无论run()还是synchTest()都是“同步的”。如果只同步其中的一个方法，那么另一个就可以自由忽视对象的锁定，并可无碍地调用。所以必须记住一个重要的规则：对于访问某个关键共享资源的所有方法，都必须把它们设为synchronized，否则就不能正常地工作。</p>

  <p>现在又遇到了一个新问题。Watcher2永远都不能看到正在进行的事情，因为整个run()方法已设为“同步”。而且由于肯定要为每个对象运行run()，所以锁永远不能打开，而synchTest()永远不会得到调用。之所以能看到这一结果，是因为accessCount根本没有变化。</p>

  <p>为解决这个问题，我们能采取的一个办法是只将run()中的一部分代码隔离出来。想用这个办法隔离出来的那部分代码叫作“关键区域”，而且要用不同的方式来使用synchronized关键字，以设置一个关键区域。Java通过“同步块”提供对关键区域的支持；这一次，我们用synchronized关键字指出对象的锁用于对其中封闭的代码进行同步。如下所示：</p>

  <p>synchronized(syncObject) {</p>

  <p>// This code can be accessed by only</p>

  <p>// one thread at a time, assuming all</p>

  <p>// threads respect syncObject's lock</p>

  <p>}</p>

  <p>在能进入同步块之前，必须在synchObject上取得锁。如果已有其他线程取得了这把锁，块便不能进入，必须等候那把锁被释放。</p>

  <p>可从整个run()中删除synchronized关键字，换成用一个同步块包围两个关键行，从而完成对Sharing2例子的修改。但什么对象应作为锁来使用呢？那个对象已由synchTest()标记出来了――也就是当前对象（this）！所以修改过的run()方法象下面这个样子：</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>synchronized(this) {</p>

  <p>t1.setText(Integer.toString(count1++));</p>

  <p>t2.setText(Integer.toString(count2++));</p>

  <p>}</p>

  <p>try {</p>

  <p>sleep(500);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>这是必须对Sharing2.java作出的唯一修改，我们会看到尽管两个计数器永远不会脱离同步（取决于允许Watcher什么时候检查它们），但在run()执行期间，仍然向Watcher提供了足够的访问权限。</p>

  <p>当然，所有同步都取决于程序员是否勤奋：要访问共享资源的每一部分代码都必须封装到一个适当的同步块里。</p>

  <p>2. 同步的效率</p>

  <p>由于要为同样的数据编写两个方法，所以无论如何都不会给人留下效率很高的印象。看来似乎更好的一种做法是将所有方法都设为自动同步，并完全消除synchronized关键字（当然，含有synchronized run()的例子显示出这样做是很不通的）。但它也揭示出获取一把锁并非一种“廉价”方案――为一次方法调用付出的代价（进入和退出方法，不执行方法主体）至少要累加到四倍，而且根据我们的具体现方案，这一代价还有可能变得更高。所以假如已知一个方法不会造成冲突，最明智的做法便是撤消其中的synchronized关键字。</p>

  <p>14.2.3 回顾Java Beans</p>

  <p>我们现在已理解了同步，接着可换从另一个角度来考察Java Beans。无论什么时候创建了一个Bean，就必须假定它要在一个多线程的环境中运行。这意味着：</p>

  <p>(1) 只要可行，Bean的所有公共方法都应同步。当然，这也带来了“同步”在运行期间的开销。若特别在意这个问题，在关键区域中不会造成问题的方法就可保留为“不同步”，但注意这通常都不是十分容易判断。有资格的方法倾向于规模很小（如下例的getCircleSize()）以及／或者“微小”。也就是说，这个方法调用在如此少的代码片里执行，以至于在执行期间对象不能改变。如果将这种方法设为“不同步”，可能对程序的执行速度不会有明显的影响。可能也将一个Bean的所有public方法都设为synchronized，并只有在保证特别必要、而且会造成一个差异的情况下，才将synchronized关键字删去。</p>

  <p>(2) 如果将一个多造型事件送给一系列对那个事件感兴趣的“听众”，必须假在列表中移动的时候可以添加或者删除。</p>

  <p>第一点很容易处理，但第二点需要考虑更多的东西。让我们以前一章提供的BangBean.java为例。在那个例子中，我们忽略了synchronized关键字（那时还没有引入呢），并将造型设为单造型，从而回避了多线程的问题。在下面这个修改过的版本中，我们使其能在多线程环境中工作，并为事件采用了多造型技术：</p>

  <p>//: BangBean2.java</p>

  <p>// You should write your Beans this way so they</p>

  <p>// can run in a multithreaded environment.</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.util.*;</p>

  <p>import java.io.*;</p>

  <p>public class BangBean2 extends Canvas</p>

  <p>implements Serializable {</p>

  <p>private int xm, ym;</p>

  <p>private int cSize = 20; // Circle size</p>

  <p>private String text = "Bang!";</p>

  <p>private int fontSize = 48;</p>

  <p>private Color tColor = Color.red;</p>

  <p>private Vector actionListeners = new Vector();</p>

  <p>public BangBean2() {</p>

  <p>addMouseListener(new ML());</p>

  <p>addMouseMotionListener(new MM());</p>

  <p>}</p>

  <p>public synchronized int getCircleSize() {</p>

  <p>return cSize;</p>

  <p>}</p>

  <p>public synchronized void</p>

  <p>setCircleSize(int newSize) {</p>

  <p>cSize = newSize;</p>

  <p>}</p>

  <p>public synchronized String getBangText() {</p>

  <p>return text;</p>

  <p>}</p>

  <p>public synchronized void</p>

  <p>setBangText(String newText) {</p>

  <p>text = newText;</p>

  <p>}</p>

  <p>public synchronized int getFontSize() {</p>

  <p>return fontSize;</p>

  <p>}</p>

  <p>public synchronized void</p>

  <p>setFontSize(int newSize) {</p>

  <p>fontSize = newSize;</p>

  <p>}</p>

  <p>public synchronized Color getTextColor() {</p>

  <p>return tColor;</p>

  <p>}</p>

  <p>public synchronized void</p>

  <p>setTextColor(Color newColor) {</p>

  <p>tColor = newColor;</p>

  <p>}</p>

  <p>public void paint(Graphics g) {</p>

  <p>g.setColor(Color.black);</p>

  <p>g.drawOval(xm - cSize/2, ym - cSize/2,</p>

  <p>cSize, cSize);</p>

  <p>}</p>

  <p>// This is a multicast listener, which is</p>

  <p>// more typically used than the unicast</p>

  <p>// approach taken in BangBean.java:</p>

  <p>public synchronized void addActionListener (</p>

  <p>ActionListener l) {</p>

  <p>actionListeners.addElement(l);</p>

  <p>}</p>

  <p>public synchronized void removeActionListener(</p>

  <p>ActionListener l) {</p>

  <p>actionListeners.removeElement(l);</p>

  <p>}</p>

  <p>// Notice this isn't synchronized:</p>

  <p>public void notifyListeners() {</p>

  <p>ActionEvent a =</p>

  <p>new ActionEvent(BangBean2.this,</p>

  <p>ActionEvent.ACTION_PERFORMED, null);</p>

  <p>Vector lv = null;</p>

  <p>// Make a copy of the vector in case someone</p>

  <p>// adds a listener while we're</p>

  <p>// calling listeners:</p>

  <p>synchronized(this) {</p>

  <p>lv = (Vector)actionListeners.clone();</p>

  <p>}</p>

  <div class="mbppagebreak"></div>
</body>
</html>
